/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hop.core.row;

/**
 * This class of static methods can be used to manipulate rows: add, delete, resize, etc... That
 * way, when we want to go for a metadata driven system with hiding deletes, over sized arrays etc,
 * we can change these methods to find occurrences.
 *
 * <p>For example, a transform adding a field to the row should always call
 *
 * <pre>
 * <a href="">public static Object[] resizeArray(Object[] objects, int newSize)</a></i>
 * </pre>
 *
 * <p>which will either physically resize the array or return the original row, in case it was
 * over-allocated and has enough slots. If a transform needs to create new rows from scratch, it
 * should use allocateRowData() which will return a somewhat over-allocated object array to fit the
 * desired number of fields.
 */
public class RowDataUtil {
  public static int OVER_ALLOCATE_SIZE = 10;

  /**
   * Allocate a new Object array. However, over allocate by a constant factor to make adding values
   * faster.
   *
   * @param size the minimum size to allocate.
   * @return the newly allocated object array
   */
  public static Object[] allocateRowData(int size) {
    return new Object[size + OVER_ALLOCATE_SIZE];
  }

  /**
   * Resize an object array making it bigger, over allocate, return the original array if there's
   * enough room.
   *
   * @param objects
   * @param newSize
   * @return A new object array, resized.
   */
  public static Object[] resizeArray(Object[] objects, int newSize) {

    if (objects != null && objects.length >= newSize) {
      return objects;
    }

    Object[] newObjects = new Object[newSize + OVER_ALLOCATE_SIZE];
    if (objects != null) {
      System.arraycopy(objects, 0, newObjects, 0, objects.length);
    }
    return newObjects;
  }

  /**
   * Resize an object array making it bigger, over allocate, always create a copy of the original
   * array, even if there's enough room in the old one.
   *
   * @param objects the original row
   * @param newSize the new size
   * @return A new object array, resized.
   */
  public static Object[] createResizedCopy(Object[] objects, int newSize) {

    Object[] newObjects;
    if (objects.length < newSize) {
      newObjects = new Object[newSize + OVER_ALLOCATE_SIZE];
    } else {
      newObjects = new Object[objects.length];
    }

    if (objects != null) {
      System.arraycopy(objects, 0, newObjects, 0, objects.length);
    }
    return newObjects;
  }

  /**
   * This method concatenates data from an array of rows, each with their own specific length.
   *
   * @param objects
   * @param lengths
   * @return The concatenated array of objects.
   */
  public static Object[] createResizedCopy(Object[][] objects, int[] lengths) {
    int size = 0;
    if (objects != null) {
      for (int i = 0; i < objects.length; i++) {
        size += lengths[i];
      }
    }
    Object[] newObjects = allocateRowData(size);

    if (objects != null) {
      size = 0;
      for (int i = 0; i < lengths.length; i++) {
        System.arraycopy(objects[i], 0, newObjects, size, lengths[i]);
        size += lengths[i];
      }
    }

    return newObjects;
  }

  /**
   * Remove an item from an Object array. This is a slow operation, later we want to just flag this
   * object and discard it at the next resize. The question is of-course if it makes that much of a
   * difference in the end.
   *
   * @param objects
   * @param index
   * @return
   */
  public static Object[] removeItem(Object[] objects, int index) {
    Object[] newObjects = new Object[objects.length - 1];
    System.arraycopy(objects, 0, newObjects, 0, index);
    System.arraycopy(objects, index + 1, newObjects, index, objects.length - index - 1);
    return newObjects;
  }

  /**
   * Add two arrays and make one new one.
   *
   * @param one The first array
   * @param the length of the row data or of it's longer, the location of the new extra value in the
   *     returned data row
   * @param two The second array
   * @return a new Array containing all elements from one and two after one another
   */
  public static Object[] addRowData(Object[] one, int sourceLength, Object[] two) {
    Object[] result = resizeArray(one, sourceLength + two.length);

    System.arraycopy(two, 0, result, sourceLength, two.length);

    return result;
  }

  /**
   * Add a single value to a row of data
   *
   * @param rowData The original row of data
   * @param the length of the row data or of it's longer, the location of the new extra value in the
   *     returned data row
   * @param extra The extra value to add
   * @return a new Array containing all elements, including the extra one
   */
  public static Object[] addValueData(Object[] rowData, int length, Object extra) {

    Object[] result = resizeArray(rowData, length + 1);
    result[length] = extra;
    return result;
  }

  /**
   * Remove a number of items in a row of data.
   *
   * @param rowData the row of data to remove from
   * @param index the index of all the items in the source table to remove. We don't check if the
   *     same index gets deleted twice!
   */
  public static Object[] removeItems(Object[] rowData, int[] index) {
    Object[] data = new Object[rowData.length - index.length];

    int count = data.length - 1;
    int removenr = index.length - 1;
    for (int i = rowData.length - 1; i >= 0; i--) {
      if (removenr >= 0 && i == index[removenr]) {
        removenr--;
      } else {
        data[count] = rowData[i];
        count--;
      }
    }

    return data;
  }
}
